认证、授权服务器
一、文章前述
当一个团队下的项目过多的时候,可能存在每个项目都有各自的认证方式,各自的用户信息库。当团队需要对用户的某些类型的信息做敏感处理、用户信息有针对性的提供等,就需要每一个项目都去修改各自的用户库、代码逻辑,并且改动过程中各自为政,可能出现同一团队信息对外展现形式不统一,导致一些用户认知系统差异并且各自为政会导致重复劳动。在这时候,认证授权服务器就应运而生,同一管理用户信息,规范管理用户信息对外授权,起到一个统一、安全的作用。
目前线上有很多主流的认证、授权方式,比如Oauth2.0,、OIDC(基于Oauth2.0)等。接下来介绍的就是目前的主流实现Oauth2.0认证授权的框架,SpringSecurity。
二、认证服务器
Spring Authorization Server 是 Spring Security 新推出的认证服务器框架。该框架也集成了OIDC,提供了客户端的注册和查询功能。下面开始介绍认证服务器的原理与配置。
整体认证流程
![认证获取用户资源时序图 (1).png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8c8587778bb476f89bf23d25df0ad60~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.image?)
1、Spring Security
Spring Authorization Server 框架是基于Spring Security 开发,所以在学习 Spring Authorization Server 配置之前,了解一下 Spring Security 框架是有必要的。
Spring Security 架构流程
Spring Security 是基于 Filter 的职责链模式,下面图片是整体的结构:
该结构为多SecurityFilterChain模式,/api/** 会一直第一个链条,其余的会按照顺序一个一个向下判断 SecurityFilterChain ,直到找到一个符合的链条。
DelefatingFilterProxy:处于 Spring-Web 中的一个委托用来执行 Spring Security 链条的前置 Filter。具体执行的是:FilterChainProxy
SecurityFilterChain:真正执行配置 Spring Security 安全流程的责任链,该责任链的可以动态配置以实现开闭原则。
Spring Security 认证配置
/**
* @author 长安
*/
@Slf4j
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {
JwtDecoder jwtDecoder;
@Autowired
public void setJwtDecoder(JwtDecoder jwtDecoder) {
this.jwtDecoder = jwtDecoder;
}
// @formatter:off
@Bean
@Order(200)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.cors().and()
// 配置路径权限
.authorizeHttpRequests(authorize ->
authorize
.requestMatchers("/auth/**").hasRole("USER")
.requestMatchers("/unAuth/**", "/login", "/favicon.ico").permitAll()
.requestMatchers(
// 另一种配置的方式
new AntPathRequestMatcher("/h2/**", HttpMethod.GET.name()),
new AntPathRequestMatcher("/h2/**", HttpMethod.POST.name())
).permitAll()
.requestMatchers("/").authenticated()
.anyRequest().authenticated()
)
.headers().frameOptions().sameOrigin()
.and()
.csrf().disable()
// 开启 formlogin ,也就是登录页面
.formLogin()
.usernameParameter("un")
.passwordParameter("pw")
// .successForwardUrl("/")
// .failureForwardUrl("/login")
.and()
// 这个 Filter 可以验证 BearerToken,解析认证
.addFilterAfter(new BearerTokenAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
;
return http.build();
}
// @formatter:on
// 密码加密方式,配置该方式后,切记数据库用户密码也要使用加密的
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(new JwtAuthenticationProvider(jwtDecoder)));
}
}
Spring Authorization Server 认证配置
Spring Authorization Server 框架里面已经配置了关于Oauth2与OIDC的过滤器与认证器。源码暂时不介绍,后续有机会介绍。
版本: org.springframework.security:spring-security-oauth2-authorization-server:1.0.1
@Configuration
public class AuthorizationServerConfiguration2 {
/**
* 配置 oauth2 相关接口的拦截执行链, 具体的接口配置在 AuthorizationServerSettings 中。
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
// 这里配置了当前 FilterChain 只会拦截oauth2相关的请求
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.authorizationEndpoint(oAuth2AuthorizationEndpointConfigurer -> {
oAuth2AuthorizationEndpointConfigurer.authenticationProviders(authenticationProviders -> {
// 配置授权码生成策略
for (AuthenticationProvider authenticationProvider : authenticationProviders) {
log.info(" consumer authenticationProvider , current -> {} ", authenticationProvider.getClass().getName());
if(authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider oAuth2AuthorizationCodeRequestAuthenticationProvider) {
oAuth2AuthorizationCodeRequestAuthenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator());
} else if(authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider oAuth2AuthorizationConsentAuthenticationProvider) {
oAuth2AuthorizationConsentAuthenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator());
}
}
});
})
.authorizationService(new InMemoryOAuth2AuthorizationService()) // 认证后token存储配置,默认在内存,可配置Redis与DB
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
)
// 使用 access_token 的方式
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
;
return http.build();
}
// 这里是配置一个内存 Client
// @Bean
// public RegisteredClientRepository registeredClientRepository() {
// RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
// .clientId("messaging-client")
// .clientSecret(new BCryptPasswordEncoder().encode("secret"))
// .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
// .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
// .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
// .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
// .redirectUri("http://127.0.0.1:8080/authorized")
// .scope(OidcScopes.OPENID)
// .scope(OidcScopes.PROFILE)
// .scope("message.read")
// .scope("message.write")
// .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
// .build();
//
// return new InMemoryRegisteredClientRepository(registeredClient);
// }
// 配置数据库查询的 Client
@Bean
public RegisteredClientRepository registeredClientRepository(@Qualifier("customJdbcTemplate") JdbcTemplate jdbcTemplate) {
return new JdbcRegisteredClientRepository(jdbcTemplate);
}
// 配置JWT的生成方式
@Bean
public JWKSource jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
// 配置JWT的生成方式
@Bean
public JwtDecoder jwtDecoder(JWKSource jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
/**
* 配置自定义的授权码 authorization_code,默认实现:OAuth2AuthorizationCodeGenerator
* - 下面简单配置成为了 UUID 的形式。
*/
public OAuth2TokenGenerator authorizationCodeGenerator() {
return context -> {
log.info(" use custom authorization_code creator ");
if (context.getTokenType() == null ||
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {
return null;
}
String authorizationCode = UUID.randomUUID().toString().replaceAll("-", "");
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
return new OAuth2AuthorizationCode(authorizationCode, issuedAt, expiresAt);
};
}
}
Resource Server 授权配置
授权服务器配置。
@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class ResourceConfig {
@Bean
@Order(-100)
SecurityFilterChain resourceSecurityFilterChain(HttpSecurity http) throws Exception {
// 配置授权服务器认证方式
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// 配置授权职责链端点
RequestMatcher requestMatcher = new AntPathRequestMatcher("/resource/**");
http.securityMatcher(requestMatcher);
http.authorizeHttpRequests(matcherRegistry -> {
matcherRegistry.anyRequest().authenticated();
});
return http.build();
}
}
配置如上,完成一个简单的基于授权码的认证授权服务器,后面还会找个时间写一个Spring Security源码解析的文章。
后续有问题可以在公众号中咨询,看到会回复。
备注
![Pasted image 20230504113822.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e7395ac52ac48058268fe32338f74fb~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.image?)
示例一个认证整体流程的Filter,如果需要自定义扩展,可以实现下面几个类
OAuth2AuthorizationEndpointFilter
OAuth2AuthorizationEndpointFilter:OAuth 2.0授权代码授予的过滤器,它处理OAuth 2.0授权请求和同意的处理。
默认接口:/oauth2/authorize
AuthenticationConverter:解析请求request中的client信息,将其封装成Authentication对象,用于后期处理。
OAuth2AuthorizationCodeAuthenticationConverter:授权码处理converter
OAuth2AuthorizationConsentAuthenticationConverter:处理同意的请求
AuthenticationManager:处理身份认证请求。默认处理器是:ProviderManager
AuthenticationProvider: 处理特定身份认证的实现。
OAuth2AuthorizationCodeAuthenticationProvider:授权码认证的provider
OAuth2AuthorizationCodeAuthenticationProvider.OAuth2AuthorizationService 配置这个参数用来解析token
|